主题
Spring AOP 与声明式事务
一、AOP 配置
Spring AOP 底层是动态代理(JDK 动态代理 / CGLIB),proxy-target-class=true 强制走 CGLIB。三种声明方式:
| 方式 | 做法 |
|---|---|
| 纯 XML | <aop:config> + <aop:aspect> + <aop:before pointcut="execution(...)"/> |
| XML + 注解 | 开启 <aop:aspectj-autoproxy proxy-target-class="true"/>,切面类用 @Aspect/@Before 等 |
| 纯注解 | 配置类加 @EnableAspectJAutoProxy,切面用 @Aspect + 通知注解 |
二、声明式事务配置
本质是 AOP 把事务增强织入到业务方法上(事务管理器 = DataSourceTransactionManager)。
| 方式 | 做法 |
|---|---|
| 纯 XML | <tx:advice> 配 propagation/isolation/read-only → <aop:advisor advice-ref pointcut="execution(...)"/> |
| XML + 注解 | <tx:annotation-driven transaction-manager="..."/> + 在类/方法上加 @Transactional |
| 纯注解 | 配置类加 @EnableTransactionManagement,方法上 @Transactional |
@Transactional 关键属性:propagation(传播)、isolation(隔离)、readOnly、timeout、rollbackFor。
三、事务传播行为(7 种,重点 3 种)
定义视角:站在被调用方法 B 的角度看"当前已有事务时 B 怎么办"。日常开发前两个(REQUIRED / SUPPORTS)最常用。
| 传播行为 | 行为 |
|---|---|
REQUIRED(默认) | 当前有事务就加入,没有就新建。B 抛异常会连 A 一起回滚 |
SUPPORTS | 当前有事务则加入,没有就以非事务方式执行 |
MANDATORY | 必须在已有事务中运行;无事务则抛 IllegalTransactionStateException |
REQUIRES_NEW | 挂起当前事务,新建一个完全独立的事务 |
NOT_SUPPORTED | 以非事务方式执行,并挂起任何现有事务 |
NEVER | 必须非事务;若已有事务则抛异常 |
NESTED | 在当前事务内开启一个嵌套子事务(基于 JDBC Savepoint) |
REQUIRED 的两种易错组合
- 外部 A 无事务、内部 B 有
REQUIRED:B 异常只回滚 B,不影响 A。 - 外部 A 有
REQUIRED、内部 B 无事务:B 异常会把 A 一起回滚(B 实际并入 A 的事务)。
REQUIRES_NEW vs NESTED(面试高频)
- REQUIRES_NEW:内外层是两个互不相干的事务。内层提交后,外层后续失败不能回滚内层;需要 JTA 事务管理器支持。
- NESTED:内层是外层的真正子事务,靠
Savepoint实现。内层失败只回滚到 savepoint(外层其余操作继续);外层失败会连内层一起回滚。仅DataSourceTransactionManager+ JDBC 3.0+ 驱动支持,且需nestedTransactionAllowed=true。 - 典型用法:NESTED 适合"主流程 + 日志记录"——日志那步失败不影响主流程,但主流程失败整体回滚。
常见误解
- "REQUIRES_NEW 内层失败会导致外层回滚" —— 错,两者完全独立。
- "NESTED 和 REQUIRES_NEW 差不多" —— 错,NESTED 外层回滚会带走内层,REQUIRES_NEW 不会。
- "声明式事务加了
@Transactional就一定生效" —— 还需proxy-target-class/代理生效、方法经 Spring 代理调用(自调用失效)等前提。
关联
- 上游:SpringAOP事务传播行为详解 · SpringAOP配置 · Spring声明式事务配置
- 同域:[Spring IOC容器初始化与循环依赖](./Spring IOC容器初始化与循环依赖)(事务/代理增强都经过
BeanPostProcessor织入,与三级缓存的getEarlyBeanReference同源) - 域地图:Java后端